import requests
import os
import time
import random
import hashlib
from urllib.parse import quote
from bs4 import BeautifulSoup
from fake_useragent import UserAgent


class BaiduImageSpider:
    def __init__(self, keywords, save_dir='images', max_num=100):
        """
        初始化爬虫参数
        :param keywords: 搜索关键词
        :param save_dir: 保存图片的目录
        :param max_num: 最大下载数量
        """
        self.keywords = keywords
        self.save_dir = save_dir
        self.max_num = max_num
        self.downloaded = 0
        self.ua = UserAgent()
        self.headers_list = [
            {'User-Agent': self.ua.random},
            {'User-Agent': self.ua.google},
            {'User-Agent': self.ua.chrome},
            {'User-Agent': self.ua.firefox},
            {'User-Agent': self.ua.safari}
        ]
        self.proxies = [
            # 可以添加多个代理服务器，格式: {"http": "http://user:pass@ip:port"}
            {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"},  # 本地代理
            {"http": "http://username:password@proxy.example.com:8080"},  # 需要认证的代理
            {"https": "https://45.67.123.89:3128"},  # 普通IP代理
            {"http": "socks5://user:pass@host:port"},  # SOCKS5代理
        ]
        self.current_proxy = None

        # 创建保存目录
        if not os.path.exists(save_dir):
            os.makedirs(save_dir)

    def get_random_header(self):
        """获取随机请求头"""
        return random.choice(self.headers_list)

    def rotate_proxy(self):
        """切换代理服务器"""
        if self.proxies:
            self.current_proxy = random.choice(self.proxies)
            print(f"切换到代理: {self.current_proxy}")

    def wait_random_time(self):
        """随机等待一段时间，避免频繁请求"""
        wait_time = random.uniform(1, 3)  # 1-3秒随机等待
        time.sleep(wait_time)

    def get_image_urls(self, page=0):
        """获取图片URL列表"""
        encoded_keyword = quote(self.keywords)
        # 百度图片搜索URL，pn参数表示页码，每页30张图片
        url = f"https://image.baidu.com/search/index?tn=baiduimage&word={encoded_keyword}&pn={page * 30}"

        try:
            # 发送请求
            response = requests.get(
                url,
                headers=self.get_random_header(),
                proxies=self.current_proxy,
                timeout=10
            )
            response.raise_for_status()  # 检查请求是否成功

            # 解析页面
            soup = BeautifulSoup(response.text, 'html.parser')
            img_tags = soup.select('img.main_img')

            img_urls = []
            for img in img_tags:
                if 'data-imgurl' in img.attrs:
                    img_urls.append(img['data-imgurl'])
                elif 'src' in img.attrs:
                    img_urls.append(img['src'])

            return img_urls

        except requests.exceptions.RequestException as e:
            print(f"请求出错: {e}")
            return []

    def download_image(self, url):
        """下载单张图片"""
        try:
            # 随机等待
            self.wait_random_time()

            # 发送图片请求
            response = requests.get(
                url,
                headers=self.get_random_header(),
                proxies=self.current_proxy,
                timeout=15
            )
            response.raise_for_status()

            # 生成文件名（使用哈希值避免重复）
            file_hash = hashlib.md5(url.encode('utf-8')).hexdigest()
            file_ext = os.path.splitext(url)[1].lower()
            if not file_ext or len(file_ext) > 5:
                file_ext = '.jpg'
            file_name = f"{file_hash}{file_ext}"
            file_path = os.path.join(self.save_dir, file_name)

            # 保存图片
            with open(file_path, 'wb') as f:
                f.write(response.content)

            self.downloaded += 1
            print(f"已下载 {self.downloaded}/{self.max_num}: {file_path}")
            return True

        except Exception as e:
            print(f"下载失败: {url}, 错误: {e}")
            return False

    def run(self):
        """运行爬虫"""
        print(f"开始爬取关键词: '{self.keywords}', 最大下载数量: {self.max_num}")

        page = 0
        while self.downloaded < self.max_num:
            # 每10页切换一次代理
            if page % 10 == 0 and self.proxies:
                self.rotate_proxy()

            # 获取图片URLs
            img_urls = self.get_image_urls(page)
            if not img_urls:
                print("未找到更多图片，爬虫结束")
                break

            # 下载图片
            for url in img_urls:
                if self.downloaded >= self.max_num:
                    break
                self.download_image(url)

            page += 1
            # 每爬取一页随机等待
            self.wait_random_time()

        print(f"爬虫完成，共下载 {self.downloaded} 张图片")


# 防封号策略说明：
# 1. 使用随机User-Agent模拟不同浏览器
# 2. 实现代理IP轮换（需要自行添加有效代理）
# 3. 请求间隔随机化（1-3秒）
# 4. 使用哈希命名避免重复下载
# 5. 完善的异常处理

# 使用示例
if __name__ == "__main__":
    spider = BaiduImageSpider(
        keywords="猫",  # 搜索关键词
        save_dir="images/cats",  # 保存目录
        max_num=50  # 最大下载数量
    )
    spider.run()